home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HPAVC
/
HPAVC CD-ROM.iso
/
PXDTUT5.ZIP
/
PXDTUT5.TXT
< prev
Wrap
Text File
|
1997-08-14
|
30KB
|
808 lines
|====================================|
| |
| TELEMACHOS proudly presents : |
| |
| Part 5 of the PXD trainers - |
| |
| SVGA using VESA 1.2 |
| |
| |
|====================================|
___---__--> The Peroxide Programming Tips <--__---___
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
Intoduction
-----------
Hi folks... It's been a while, I know, but I have been busy living my REAL
life :) (YEAH, I know : REAL LIFE SUXX!)
There have been quite a few people mailing me about my last two tutorials and
one of them made me discover a little error in my gouraud routines.
Actually it's not an error - I just presume that there is a maximum of 64
colors in the shading table. If using more a few variables / regs will overflow
resulting in graphical errors. The problems can be fixed by reducing the
shl 8 statement to shl 7 when calculating the fixed point step values.
Then later on when the vars has been calculated as 9.7 fixed point values you
just have to multiply them by two to get them back to 8.8 fixed point.
Now things should work correct.
Also, in the gouraud polygonside scanner change the type of the variable
"color" from integer to word.
This tutorial will be on SVGA graphic using the VESA 1.2 BIOS implemention.
In the past I have seen quite a few shareware SVGA programming libs - but so
far all of them has sucked pretty much. Either they presume a granularity of
64K - or they are written in pure asm without any decent comments - making
them virtually impossible to learn from.
Or they use BIOS calls to switch banks which suck just as much as code without
comments.
So what we'll do is to build a small pascal/asm SVGA lib that supports *ALL*
SVGA cards on the market (at least we can try :) ).
Also I'll try to explain everything about SVGA in a way so that the reader
actually understands VESA-coding after reading this text.
If you are bored with theory stop reading right now and skip to the headline
called "THE CODE". But I strongly recommend you to read the entire text -
otherwise you'll be coding VESA graphics in half an hour but not understand
what you're doing :)
BTW. Some lamer seems to find it extremely funny to reset the hit-counter on
my homepage. If you're reading this : Please don't do it again - it makes me
very unhappy and I lie fever-striken on my bed for several days each time
it happen.
If you really enjoy resetting counters THAT much drop me a mail and I'll code
you an internet-hit-counter-resetter-simulater FOR FREE!!!!!!!
If any of you NON-lamers who reads this - you know, the kind of people who
spend their time actually CODING instead of resetting counters, spreading
virusses and beating their little brothers - I would like to know if any of
you know how to make a hit-counter that cannot be reset by the weak-minded
people of the net.
If you want to get in contact with me, there are several ways of doing it :
1) E-mail me : tm@image.dk
2) Snail mail me : Kasper Fauerby
Saloparken 226
8300 Odder
Denmark
3) Call me (Voice ! ) : +45 86 54 07 60
Get this serie from the major demo-related FTP-sites - currently :
GARBO ARCHIVES (forgot the address) : /pc/programming/
ftp.teeri.oulu.fi : /msdos/programming/docs/
ftp.cdrom.com : something with demos/incomming/code.....
Or grap it from my homepage :
Telemachos' Codin' Corner http://www.image.dk/~tm
WHAT IS VESA AND WHY DO WE NEED IT ?
------------------------------------
Well... as you all know the standard mode $13 is a linear mode taking up
64000 bytes of memory. This fits into a single segment and therefore it is
extremely easy to code. When mode $13 was introduced all agreed on using
segment $a000 as base for the mode. Therefore our mode $13 code will work on
EVERY SINGLE VGA-card available.
When SVGA was introduced for the first time it was IBM who developed a very
expensive GFX-card capable of doing extremely high resolutions - for that time,
that is.
But IBM never released the register information for programming the card.
In time other companies developed SVGA cards - each with their own way of
laying out the gfx-cards registers. The result was that it became almost
impossible to code applications for SVGA - if the application was to support
the different gfx cards the programmers had to do routines for EVERY SINGLE
gfx card on the market. The result was that most programmers chosed not to
support SVGA at all. The high resolution was'nt worth the time spend on doing
the code.
VESA saw the problem and came up with a BIOS extension that supplied some
standard routines for coding the different SVGA cards available at the time
being.
For a long time it has been VESA 1.2 that has been the standard in SVGA coding.
But now UniVBE 5.3 has been released making VESA 2.0 available on almost every
graphic card out there.
But for us pascal coders VESA 2.0 is pretty uninterresting. First of all :
The main improvement in VESA 2.0 if something called Linear Frame Buffer (LFB)
This means that you map your entire VGA-memory into one single linear buffer.
Then you can adress each pixel on the SVGA screen lineary - just as in good
old mode $13. But this is obviously only available to those coding in protected
mode - and I take it that most of my readers are'nt.
Another thing is that most gfx cards out there only has VESA 1.2 installed -
so in order to use VESA 2.0 you'll have to assume that the user owns UniVBE 5.3
So for now we'll stick to good old VESA 1.2
THE WORKING OF A SVGA CARD - THE THEORY FOR OUR CODE
-----------------------------------------------------
As mentioned before each pixel takes up 1 byte of memory. In mode $13 this is
OK as the screen is only 320 X 200 = 64000 bytes. This way the video memory
needed for a single screen of graphic fits into one single segment.
But a SVGA mode takes up much more space. Lets take the standard 640X480 SVGA
mode. This mode takes up 640 X 480 = 307200 bytes = 4,6875 segments of RAM.
Hmm... how do we adress all this memory. The modenr for this mode is $101
Try and set this mode using the following VESA code : (I'll explain the code
later on)
asm
mov ah,4Fh
mov al,02h
mov bx,$101
int 10h
end;
Now try and address the video mem via the good old $a000 segment.... Yes, just
fill the entire segent with some color. Woila! You have just plotted your first
pixels in 640 X 480 X 256. But as you might have discovered you are limited to
accessing only the uppermost part of the screen. The narrow band of graphic you
see takes up exactly 1 segment of memory.
What you have just discovered is the greatest pain in SVGA programming :
the need of windowing. Imagine the entire SVGA-memory laid out lineary. You can
form it to many different sizes. One size is 640 pixels wide, another 800 and
yet another 1024. But all the time you only have access to 1 segment of the
memory. Think of this segment as a window you can look through into the entire
video memory. Fortunately you can move this window to many different positions
in the video memory. If you move it around enough you'll discover that you can
see every byte in video memory through it - just only one segment at a time.
We call all those different window positions for banks. Well, VESA does provide
us with standard procedures to set the graphic modes, test them and move the
bank window but still SVGA is a little complicated. First of all there is
the GRANULARITY of the video card. The granularity decides how the window can
be moved around in the video memory. A granularity of 64K means that the window
can be moved only in 64K chunks - ie. an entire window at a time. Some cards
have finer granularity - Cirrus Logic fx. has a granularity of 4K which allows
the windows to overlap. Each window position is called a bank even though some
of the memory can be accesed through multiple window positions.
The window size is an entirely different matter from the granularity. On most
cards the window size is 64K (actually on every card I have seen so far) but
some cards implement 128K. This is however not important to us as the greatest
granularity is 64K - so as long the window is not below 64K in size we can
acces all memory by moving the window.
It is pretty safe to assume that the video memory is accesed from the $a000
segment but to be sure one should always test this by making a BIOS call before
trying to write to memory. Some cards have multiple windows - We call them
window A and Window B. This is because some cards have ReadOnly access in one
window and WriteOnly in another. So this is another thing to check : Can we
do both our reads and writes in the same window ?
THE VESA FUNCTIONS EXPLAINED
-----------------------------
All VESA functions is reached from a sub-function of the BIOS interrupt $10.
This subfunction is 4Fh so in all interrupt calls we must load the ah register
with 4Fh and the al register with the VESA function number we want.
I'll just run over the different VESA BIOS functions and then do the code in
the CODE section.
Function 00h - Return SVGA info
---------------------------------
Input : AH : 4Fh
AL : 00h
ES:DI : Pointer to 256bytes buffer.
Returns : AX : Status
The status register is set up as follows :
AL == 4Fh: Function is supported
Al != 4Fh: Function is not supported
AH == 00h: Function call successful
AH == 01h: Function call failed
Now, as we see we load ah with our VESA subfunction number and AL with the
VESA function number. ES:DI must point to a 256bytes buffer. This can be any
buffer of this size. Fx. buffer : Array[0..255] of byte;
But using a buffer like this leaves us with an enormous amount of converting
from byte to word and so on... So I'll give you a structure to use instead :
TYPE
ListOfAvailModesT = Array[0..255] of word; {terminated by -1 ($FFFF) }
ListOfAvailModesP = ^ListOfAvailModesT;
VESAInfoT = record
VESASignature : array[0..3] of byte;
VESAVersion : word;
OEMStringPtr : Pchar;
Capabilities : array[0..3] of byte;
VideoModePtr : ListOfAvailModesP;
TotalMemory : word;
Reserved : Array[0..235] of byte;
end;
The VESASignature is 4 bytes and when converted to a string they must form the
word 'VESA'. Otherwise it is not a valid VESA-structure.
The VESAVersion is a word with the high-byte being the major version number and
the low byte being the minor version number.
OEMStringPtr is a pointer to a 0-terminated String which contains the ident
string of the SVGA card. But we are in luck - in pascal the type Pchar means
a pointer to such a string, so we don't have to think any more about this one.
Capabilities is mostly unused. If bit 0 in the first byte is set it means that
the DAC can be reprogrammed to another DAC width (standard is 6-bit)
VideoModePtr : Now this is a little tricky. This is a pointer to a list of
words - each word containing a valid graphic mode on the card. The list is
terminated by -1 ($FFFF).
TotalMemory contains the number of 64K blocks of RAM installed on the card.
The reserved field is reserved for future VESA expansions.
This function is the first function your application should run for checking
if VESA is available at all.
Function 01h - Return VESA MODE information
-------------------------------------------
Input : AH = 4Fh
AL = 01h
CX = Mode number (must be from the mode list from func 00h)
ES:DI = pointer to 256 bytes buffer
Returm AX = Status
Again I'll give you a structure to use for the 256 bytes buffer. I'll leave out
some fields for true-color stuff that we will not discuss in this text.
TYPE
ModeAttributesT = (Available,
Reserved,
BIOSFunctionsSupport,
color,
graphic,
bit5,
bit6,
bit7,
bit8);
WindowAttributesT = (Supported,R,W); {rest of the bits are unused...}
VESAModeInfoT = record
ModeAttributes : set of ModeAttributesT;
WinAAttributes : set of WindowAttributesT;
WinBAttributes : set of WindowAttributesT;
WinGranularity : word;
WinSize : word;
WinASegment : word;
WinBSegment : word;
BankSwitch : procedure;
BytesPerScanLine : word;
{Xtended Information}
XResolution : word;
YResolution : word;
XCharSize : byte;
YCharSize : byte;
NumberOfPlanes : byte;
BitsPerPixel : byte;
NumberOfBanks : byte;
MemoryModel : byte;
BankSize : byte;
NumberOfImagePages : byte;
Reserved : Array[0..225] of byte;
{reserved is for something we won't think about now...
true color and stuff... }
end;
WOW.... This sure is a tough one to get through. Well.. to start from the top.
ModeAttributes : This is a word with every bit meaning something different.
That's why I made the ModeAttributesT. It contains different information about
the mode from CX. If bit 0 is set the mode is available - if not : Your guess.
The rest of this field should be obvious from the names in ModeAttributes :
is it a color mode ? Is it a graphic or text mode? The BIOSSupport field
decided if you can use BIOS scroll, TTY output and BIOS pixel output in the
selected mode.
WinAAttributes and WinBAttributes : These are pretty important as they tell you
if you can read or write to the two windows (A and B). Some cards has only one
window availble so a window CAN be Read AND Writeable.
WinGranularity : This field contains the granularity in KB.
WinSize : The Size of the window in KB.
WinASegent and WinBSegment : This decides where in memory the window base is
placed. Usually WinASegment will be $A000 - but check it out to be sure.
If a window reports $0000 as address DON'T use that address :)
BankSwitch : This is a pointer to the hardware bankswitch function. A far call
can be made to this address for quick bankshifting. Lucky us - pascal allows us
to use the type "procedure" here so we don't have to worry about the pointer
stuff... just call this field from an assembler routine. More on this facility
under function 05h later on.
BytesPerScanLine : The logical number of bytes pr y-line in memory. This is
usually the same as the maximum x-value in the graphic mode.
The Xtended fields should be pretty self-explaining. One word of warning though.
I have experienced some problems with the NumberOfBanks and BankSize fields on
my Cirrus Logic card. Don't trust these fields blindly.
function 02 - Set VESA mode
----------------------------
Input : AH = 4Fh
AL = 02h
BX = Video Mode
bit 15 : 1 = Don't clear video RAM
0 = Clear video RAM
Returns : AX = Status
OK.. Use this one to set the VESA modes. The modes is as follows :
GRAPHICS TEXT
15-bit 7-bit Resolution Colors 15-bit 7-bit Columns Rows
mode mode mode mode
number number number number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100h - 640x400 256 108h - 80 60
101h - 640x480 256
109h - 132 25
102h 6Ah 800x600 16 10Ah - 132 43
103h - 800x600 256 10Bh - 132 50
10Ch - 132 60
104h - 1024x768 16
105h - 1024x768 256
106h - 1280x1024 16
107h - 1280x1024 256
10Dh - 320x200 32K (1:5:5:5)
10Eh - 320x200 64K (5:6:5)
10Fh - 320x200 16.8M (8:8:8)
110h - 640x480 32K (1:5:5:5)
111h - 640x480 64K (5:6:5)
112h - 640x480 16.8M (8:8:8)
113h - 800x600 32K (1:5:5:5)
114h - 800x600 64K (5:6:5)
115h - 800x600 16.8M (8:8:8)
116h - 1024x768 32K (1:5:5:5)
117h - 1024x768 64K (5:6:5)
118h - 1024x768 16.8M (8:8:8)
119h - 1280x1024 32K (1:5:5:5)
11Ah - 1280x1024 64K (5:6:5)
11Bh - 1280x1024 16.8M (8:8:8)
Function 03h - Return current Video Mode
-----------------------------------------
Input : AH = 4Fh
AL = 03h
Returns : AX = Status
BX = Current Video mode
Not much to say about this one I guess...
Function 05h - Set Window position / Bank Switch
-------------------------------------------------
Input : AH = 4Fh
AL = 05h
BH = 00h
BL = Window nr
Window A = 0
Window B = 1
DX = Window position in granularity units.
This means bank number.
Returns : AX = Status
Now, this routine sets the window position that we talked about earlier. The
key to making a good VESA unit is to make certain that it is the correct
bank numbers that is passed to this function. On a SVGA card with a granularity
of 64K each bank matches one segment of memory and one potion of the screen.
If we want to clear out Screen we usually selects bank nr 0 and then fill
the $A000 segment with one color. Then we want to move on to the next section
of the screen. So we increase our banknr by 1 and do the same trick. But WAIT
A SECOND!! This will ONLY work with granunarities of 64K. 'Cause on cards with
a granularity of fx. 4K we might have set the window to bank 0. And we might
have filled the entire window - but by doing this we have filled SEVERAL banks!
With a granularity of 4K we have filled 64K / 4K = 16 banks! So the next bank
we should set should NOT be 1 but 16!
This is where most errors I have seen in SVGA libs lies.
As we all know calling BIOS is pretty SLOW! And with small granularities we
might have to do it MANY times during a screen-update so we're pretty lucky
that we have the brain to use the far function address we got from function
01h.
When using it we don't have to set the AX register before call. Just the BX
and DX register. Beware though : Both AX and DX is destroyed in the call so
be sure to save the information in them if you wanna reuse them.
Function 06h - Set/Get logical Scanlenght
------------------------------------------
Input : AH = 4Fh
AL = 06h
BL = 00h {Set scanlength}
CX = Desired width in pixels
Returns : AX = Status
BX = Bytes per Scanline
CX = Actual pixels per Scanline
DX = Maximum number of scanlines based on mode and memory
Input : AH = 4Fh
AL = 06h
BL = 01h {Get scanlength}
Returns : AX = Status
BX = Bytes per Scanline
CX = Actual pixels per Scanline
DX = Maximum number of Scanlines.
Now this function can be VERY useful. It allows you to set a logical width
in video memory. It is based on this width that you calculate the offset for
a pixel - just like in mode $13h.
If you set this value to a power of 2 you can make sure that there will never
be any bank-crossing along a horizontal scanline. This can speed up your code
if you update the screen Scanline per Scanline 'cause you only have to do a
bank check once per Scanline - and not once per pixel as you would in fx. mode
640 X 480!
Also you can use function 07h to place the actual screen anywhere in this
logical memory and this way do hardware scrolls!
If you plan on using 640 X 480 and you know that your video card has 1MB of RAM
installed you could set the logical scanlength to 1024 and thereby ease the
offset calculation from :
ofs := y shl 9 + y shl 7 + X
to
ofs := y shl 10 + X;
It's a waste of memory, but if you are smart you store something in the memory
outside the screen.
Function 07h - Set / Get display start
----------------------------------------
Input : AH = 4Fh
AL = 07h
BH = 00h
BL = 00h {set display start}
CX = Xpos
DX = Ypos
Returns : AX = Status
Input : AH = 4Fh
AL = 07h
BL = 01h {get display start}
Returns : AX = Status
BH = 00h
CX = Xpos
DX = Ypos
This one sets the position of the screen in video memory.
Function 08h - Set/Get DAC pallette control
--------------------------------------------
Input : AH = 4Fh
AL = 08h
BL = 00h {Set DAC pallette width}
BH = desired number of bits per primary color
Returns : AX = Status
BH = Current number of bits per primary color
Input : AH = 4Fh
AL = 08h
BL = 01h {get DAC pallette width}
Returns : AX = Status
BH = Current number of bits per primary color
This function sets the DAC pallette width - check if this function is
available by checking bit 0 in the Capabilities field in VESAInfo.
THE CODE :
-----------
First lets get the VESA information and save it in a variable called
VESAInfo :
FUNCTION GetVESAInfo : boolean;
Assembler;
asm
mov ah,4Fh {The usual VESA sub-function number}
mov al,00h {This is VEAS function 00h }
lea di,VESAInfo {load the address of VESAInfo into es:di}
int 10h {call the BIOS interrupt}
cmp ah,0 {ah returns 0 if succes}
jne @fail
mov ax,1
jmp @out
@fail:
mov ax,0 {the ax register will be passed to the function}
@out:
end;
The same way we get the Info for the mode we wanna set :
FUNCTION GetVESAModeInfo(mode : word) : boolean;
Assembler;
asm
mov ah,4Fh
mov al,01h {VESA function 01h}
xor cx,cx
mov cx,mode {load the desired mode in cx}
lea di,VESAModeInfo {load the address to VESAModeInfo into es:di}
int 10h {call BIOS video interrupt}
cmp ah,0
jne @fail
mov ax,1
jmp @out
@fail:
mov ax,0 {we put the result in ax }
@out:
end;
OK... Now we need to be able to switch the banks :
Procedure SetBank(nr : word);
Assembler;
asm
xor bl,bl {Window A selected}
mov dx,nr
call [VESAModeInfo.BankSwitch] {here we call the bankSwitch procedure}
mov dx,nr {dx is destroyed by far procedure call}
mov cur_page,dx {update global page variable}
end;
Now this routine assumes that Window A is Read/Writeable. BX desides which
window is active bl = 0 means Window A and bl = 1 means Window B.
It is a good idea to have a global variable that keeps track of which bank is
active at the moment so you don't change to the bank that is allready active!
This done I think we need to set the mode up :
FUNCTION SetVESAMode(mode : word) : boolean;
Assembler;
asm
mov ax,03h
int 10h {start from textmode}
mov ah,4Fh
mov al,02h
mov bx,mode
int 10h
cmp ah,0
jne @fail {set the VESA mode through VESA function 02h}
mov ah,4Fh
mov al,05h
xor bx,bx
mov dx,0
int 10h {set bank 0 from BIOS call this time - to be safe }
mov cur_page,0 {initialize global page variable to bank 0}
cmp ah,0
jne @fail
mov ax,1
jmp @out
@fail:
mov ax,0 {return fail or succes to function}
@out:
end;
Now lets plot a pixel, shall we ??
PROCEDURE VESAputpixel(x, y : WORD; c : BYTE);
VAR
bank : WORD;
offs : longint;
BEGIN
offs := LONGINT(y) * VESAModeInfo.Xresolution + x;
bank := offs SHR (16-BankShiftModifier);
offs := offs - (bank SHL (16-BankShiftModifier));
IF bank <> Cur_page THEN {page = global var - active page}
BEGIN
cur_page := bank;
ASM
Xor bl,bl
mov dx,bank
call [VESAModeInfo.BankSwitch]
END;
END;
ASM
MOV AX, $A000
MOV ES, ax
MOV DI, WORD(offs)
MOV AL, c
MOV ES:[DI], AL
END;
END;
Now in this routine I use a variable called BankShiftModifier. This is because
the routine assumes a granularity of 64K when using the shr 16 and shl 16
statements. With a granularity of fx. 4K it should be shr 12 and shl 12.
So we'll have to calculate the modifier when we recieve the information about
the granularity from function 01h.
Case VESAModeInfo.WinGranularity of
1 : BankShiftModifier := 6;
2 : BankShiftModifier := 5;
4 : BankShiftModifier := 4;
8 : BankShiftModifier := 3;
16 : BankShiftModifier := 2;
32 : BankShiftModifier := 1;
64 : BankShiftModifier := 0;
end; {With granularities smaller than 64 but with page-size 64K the
banknr for each full segment of SVGA graphic will not be 0,1,2,3
but multiplied with BankMult ( 2^BankShiftModifier for easy bit
shifting) }
BankMult := 64 div VESAModeInfo.WinGranularity;
Now, I know of granularities of 64K, 32K and 4K... But it can't hurt to have
the other posibilities as well... you never know with SVGA.
The Variable BankMult is used for our next routine - The clear routine.
As mentioned when discussing function 05h the banks won't be 0,1,2,3,4 and so
on when dealing with granularities smaller than 64K. Each Segment filled is
the same as 16 banks filled with a granularity of 4K
So here goes : The ClearScreen routine :
PROCEDURE ClearScreen(color : byte);
VAR
Xres, Yres : longint;
number_of_segments : longint;
i : integer;
BEGIN
Xres := VESAModeInfo.Xresolution;
Yres := VESAModeInfo.Yresolution;
number_of_segments := Round(((Xres * Yres)/65536)+0.5)-1;
for i := 0 to number_of_segments do
BEGIN
SetBank(i*BankMult);
ASM
mov cx, 32768;
mov ax,$A000
mov es,ax
xor di,di
mov al,[color]
mov ah,al
rep stosw
END;
END;
END;
And now the final two small routines :
Procedure SetLogicalScanline(width : word);
Assembler;
asm
mov ah,4Fh
mov al,06h
mov bl,0
mov cx,width
int 10h
end;
Procedure SetScreenPosition(x,y : word);
Assembler;
asm
mov ah,4Fh
mov al,07h
xor bx,bx
mov cx,[x]
mov dx,[y]
int 10h
end;
LAST REMARKS
-------------
Well, that's about all for now.
Hope you found this doc useful - and BTW : If you DO make anything public using
these techniques please mention me in your greets or where ever you se fit.
I DO love to see my name in a greeting :=)
Now, this done you should all be able to code some pretty stunning SVGA games/
demos. We can no longer say anything else : SVGA is the future of all graphic
programming.
A few things to take note of : All the routines presented here in this text are
very general. The ClearScreen will do any resolution - same thing with the
putpixel routine. But I have sacrificed speed to achieve this! If you use the
routines in games/demos where you KNOW what resolution it will run at you
should modify the routines and optimize them to meet those specific resolutions!
Fx. I use a normal * to calculate the offset in the putpixel. This should off
cause be changed to shl's when dealing with a predefined gfx-mode.
But what now ??
If you have any good ideas for a subject you wish to see a tutorial on please
mail me. If I like the idea (and know anything about it :) ) I'll write a
tut on it.
I have spoken of a tutorial on interrupts for quite a while now....
maybe it'll come out soon - but I also plan on doing some graphical effects.
MAIL ME!!
Keep on coding...
Telemachos - August '97.